/* Copyright (C) 2007-2008  klevgert  http://code.google.com/p/db4o-netbeans/

This file is part of the db4o-netbeans module suite.

db4o-netbeans is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL 
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.

db4o-netbeans is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. */

package com.db4o.nb.editor.dialogs;

import com.db4o.ObjectContainer;
import com.db4o.nb.util.ExtensionFileFilter;
import com.db4o.nb.util.PreferencesUtil;
import com.db4o.reflect.ReflectClass;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;

/**
 *
 * @author  klevgert
 */
public class SelectTypeDialog extends javax.swing.JDialog {
  
  /** A return status code - returned if Cancel button has been pressed */
  public static final int RET_CANCEL = 0;
  /** A return status code - returned if OK button has been pressed */
  public static final int RET_OK = 1;

  static final String LEAF_ICON_PATH = "com/db4o/nb/editor/resources/class.png";
  
  public static int CLASS_TYPE = 0;
  public static int NO_CLASS_TYPE = 1;

  private int returnStatus = RET_CANCEL;
  private String selectedClass = "";
  private ObjectContainer objectContainer;
  
  Thread readThread;
  
  /** Creates new form SelectTypeDialog */
  public SelectTypeDialog(java.awt.Frame parent, boolean modal) {
    super(parent, modal);
    initComponents();

    this.setLocationRelativeTo(null);
    
    // Update class icon
    Image leafImage = Utilities.loadImage(LEAF_ICON_PATH);
    Icon leafIcon = new ImageIcon(leafImage);
    DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer)treeTypes.getCellRenderer();
    renderer.setLeafIcon(leafIcon);
    
    String title = NbBundle.getMessage(this.getClass(), "Scanning");
    final DefaultTreeModel scanModel = new DefaultTreeModel(new DefaultMutableTreeNode(title));
    treeTypes.setModel(scanModel);

    Runnable runnable = new Runnable() {
      public void run() {
        
        String title = NbBundle.getMessage(this.getClass(), "Root");
        DefaultMutableTreeNode root = new DefaultMutableTreeNode( new NodeInfo(title,NO_CLASS_TYPE) );
        addClassesFromDatabase(root);
        addLibrariesFromPreferences(root);
        addLibrariesFromClasspath(root);    

        final DefaultTreeModel model = new DefaultTreeModel(root);

        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            treeTypes.setModel(model);
            okButton.setEnabled(true);
          }
        });
        
        SelectTypeDialog.this.treeTypes.setModel(model);
      }
    };
    readThread = new Thread(runnable);
    readThread.start();
    
  }
  
  public ObjectContainer getObjectContainer() {
    return this.objectContainer;
  }
  
  public void setObjectContainer(ObjectContainer oc ) {
    this.objectContainer = oc;
  }
  
  /** @return the return status of this dialog - one of RET_OK or RET_CANCEL */
  public int getReturnStatus() {
    return returnStatus;
  }
  
  public String getSelectedClass() {
    return selectedClass;
  }
  
  /** This method is called from within the constructor to
   * initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
  private void initComponents() {
    okButton = new javax.swing.JButton();
    cancelButton = new javax.swing.JButton();
    jScrollPane1 = new javax.swing.JScrollPane();
    treeTypes = new javax.swing.JTree();
    newTypeButton = new javax.swing.JButton();

    setTitle("Select Type...");
    addWindowListener(new java.awt.event.WindowAdapter() {
      public void windowClosing(java.awt.event.WindowEvent evt) {
        closeDialog(evt);
      }
    });

    okButton.setText("OK");
    okButton.setEnabled(false);
    okButton.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        okButtonActionPerformed(evt);
      }
    });

    cancelButton.setText("Cancel");
    cancelButton.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        cancelButtonActionPerformed(evt);
      }
    });

    treeTypes.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() {
      public void valueChanged(javax.swing.event.TreeSelectionEvent evt) {
        treeTypesValueChanged(evt);
      }
    });
    treeTypes.addMouseListener(new java.awt.event.MouseAdapter() {
      public void mouseClicked(java.awt.event.MouseEvent evt) {
        treeTypesMouseClicked(evt);
      }
    });

    jScrollPane1.setViewportView(treeTypes);

    newTypeButton.setText("New...");
    newTypeButton.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        newTypeButtonActionPerformed(evt);
      }
    });

    org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
      layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
      .add(layout.createSequentialGroup()
        .addContainerGap()
        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
          .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
            .add(newTypeButton)
            .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
            .add(okButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 67, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
            .add(cancelButton))
          .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 408, Short.MAX_VALUE))
        .addContainerGap())
    );

    layout.linkSize(new java.awt.Component[] {cancelButton, okButton}, org.jdesktop.layout.GroupLayout.HORIZONTAL);

    layout.setVerticalGroup(
      layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
      .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
        .addContainerGap()
        .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 307, Short.MAX_VALUE)
        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
          .add(cancelButton)
          .add(okButton)
          .add(newTypeButton))
        .addContainerGap())
    );
    pack();
  }// </editor-fold>//GEN-END:initComponents

  private void treeTypesMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_treeTypesMouseClicked

    if( evt.getClickCount() == 2 ) {

      retrieveSelectedClass();
      doClose(RET_OK);
      
    }
  }//GEN-LAST:event_treeTypesMouseClicked

  private void newTypeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTypeButtonActionPerformed

    // Request file name from user
    JFileChooser fileChooser = new JFileChooser(".");
    fileChooser.setFileFilter(new ExtensionFileFilter("JAR", new String[] { "JAR" }));
    int status = fileChooser.showOpenDialog(null);
    if (status == JFileChooser.CANCEL_OPTION ) {
      return;
    }      
    File selectedFile = fileChooser.getSelectedFile();

    // Create complete path
    String fileName = selectedFile.getAbsolutePath();
 
    // Get the root node
    DefaultMutableTreeNode root = 
        (DefaultMutableTreeNode) treeTypes.getModel().getRoot();
    
    // Add the classes 
    addClassesFromJar(fileName, root);
    
    String classpath = PreferencesUtil.getClasspathSettings();
    classpath += ";" + fileName;
    PreferencesUtil.setClasspathSettings(classpath);

    final DefaultTreeModel model = new DefaultTreeModel(root);
    treeTypes.setModel(model);
    
  }//GEN-LAST:event_newTypeButtonActionPerformed

  private void treeTypesValueChanged(javax.swing.event.TreeSelectionEvent evt) {//GEN-FIRST:event_treeTypesValueChanged
// TODO add your handling code here:
    
  }//GEN-LAST:event_treeTypesValueChanged
  
  private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
    
    retrieveSelectedClass();
    doClose(RET_OK);
    
  }//GEN-LAST:event_okButtonActionPerformed

  private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
    doClose(RET_CANCEL);
    
    try {
      if( readThread.isAlive() ) {
        readThread.join(10000);
      }
    } catch (InterruptedException ex) {
      ex.printStackTrace();
    }
    
  }//GEN-LAST:event_cancelButtonActionPerformed
  
  /** Closes the dialog */
  private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog
    doClose(RET_CANCEL);
  }//GEN-LAST:event_closeDialog
  
  private void doClose(int retStatus) {
    returnStatus = retStatus;
    setVisible(false);
    dispose();
  }

  /**
   * Adds the classes already stored in the database.
   * @param root      tree node to add the jar and its classes
  */
  private void addClassesFromDatabase(final DefaultMutableTreeNode root) {
    
    try {
      DefaultMutableTreeNode dbNode = new DefaultMutableTreeNode( new NodeInfo("Database",NO_CLASS_TYPE) );
      root.add(dbNode);
      
      ReflectClass[] classes = this.objectContainer.ext().knownClasses();
      for (int i = 0; i < classes.length; i++) {
        String clsName = classes[i].getName();
        if( !PreferencesUtil.isSuppressedPackage(clsName) ){
          DefaultMutableTreeNode node = new DefaultMutableTreeNode( new NodeInfo(clsName) );
          dbNode.add(node);
        }
      }
      if( dbNode.getChildCount() == 0 )  {
        
          String name = NbBundle.getMessage(this.getClass(), "NoClassesFound");
          DefaultMutableTreeNode node = 
              new DefaultMutableTreeNode( new NodeInfo(name,NO_CLASS_TYPE));
          dbNode.add(node);
      }
      
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  /**
   * Adds the libraries configured in the preferences to the tree.
   * @param root      tree node to add the jar and its classes
   */
  private void addLibrariesFromPreferences(final DefaultMutableTreeNode root) {
    
    String[] prefLibs = PreferencesUtil.getSpecifiedLibraries();
    for (int i = 0; i < prefLibs.length; i++) {
      addClassesFromJar(prefLibs[i], root);
    }
  }

  /**
   * Looks up the classpath settings and adds the libraries from the 
   * classpath to the tree.
   * @param root      tree node to add the jar and its classes
   */
  private void addLibrariesFromClasspath(final DefaultMutableTreeNode root) {
    String classpath = System.getenv("CLASSPATH");
    String[] libs = classpath.split(";");
    for (int i = 0; i < libs.length; i++) {
      addClassesFromJar(libs[i], root);
    }
  } 
  
  /**
   * Gets the classes from a jar and adds them to the tree model.
   * @param jarFile   name of jar file
   * @param root      tree node to add the jar and its classes
   */
  private void addClassesFromJar(final String jarFile, final DefaultMutableTreeNode root) {
    try {
      JarFile jar = new JarFile(jarFile);
      
      // First add a node for the jar file itself
      DefaultMutableTreeNode jarNode = 
          new DefaultMutableTreeNode( new NodeInfo(jarFile, NO_CLASS_TYPE));
      root.add(jarNode);
      
      // enumerate the elements of the jar file
      Enumeration enumeration = jar.entries();
      while( enumeration.hasMoreElements() ) {
        JarEntry jarEntry = (JarEntry) enumeration.nextElement();
        String name = jarEntry.getName();
        
        // Check if it is a class, ignore inner classes
        if (name.indexOf(".class") > 0 && !(name.indexOf("$") > 0)) {
          String clsName = name.substring(0, name.lastIndexOf(".")).replace('/', '.');
          if( !PreferencesUtil.isSuppressedPackage(clsName) ){
            DefaultMutableTreeNode node = new DefaultMutableTreeNode( new NodeInfo(clsName));
            jarNode.add(node);
          }
        }
      }
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }

  /**
   * determines the selected class.
   */
  private void retrieveSelectedClass() {
   
    selectedClass = "";
    if( treeTypes.getSelectionCount() > 0 ) {
      
      DefaultMutableTreeNode selNode = (DefaultMutableTreeNode)treeTypes.getSelectionPath().getLastPathComponent();
      if( selNode.getUserObject() != null  ) {
        NodeInfo info = (NodeInfo) selNode.getUserObject();
        if( info.type == CLASS_TYPE ) {
          selectedClass = (String) selNode.toString();
        }
      }
    }
  }
  
  /**
   * @param args the command line arguments
   */
  public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new SelectTypeDialog(new javax.swing.JFrame(), true).setVisible(true);
      }
    });
  }
 
  /**
   * Instances of NodeInfo are attached as user object to tree nodes.
   * 
   */
  private class NodeInfo {

    public String displayName;
    public int type = CLASS_TYPE;

    public NodeInfo(String name) {
        this.displayName = name;
    }
    public NodeInfo(String name, int type) {
        this.displayName = name;
        this.type = type;
    }

    public String toString() {
        return this.displayName;
    }
  }  
  
  // Variables declaration - do not modify//GEN-BEGIN:variables
  private javax.swing.JButton cancelButton;
  private javax.swing.JScrollPane jScrollPane1;
  private javax.swing.JButton newTypeButton;
  private javax.swing.JButton okButton;
  private javax.swing.JTree treeTypes;
  // End of variables declaration//GEN-END:variables
  
  
}
